package server

import (
	"embed"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/aler9/gortsplib"
	"github.com/gin-gonic/gin"
	"github.com/pion/interceptor"
	"github.com/pion/rtp"
	"github.com/pion/rtp/codecs"
	"github.com/pion/webrtc/v3"
	"github.com/pion/webrtc/v3/pkg/media/h264reader"
)

type WebRtcEngine struct {
	api *webrtc.API

	streamPushList    map[string]*WeSocketWebrtcConnectionPush
	streamPushListMux sync.Mutex

	fileContent  embed.FS
	rtspPort     int
	rtpPort      int
	rtcpPort     int
	httpsPort    int
	rtp2RtspPort int

	rtspServer *RtspServerHandler
}

func NewWebRtcEngine(muxUdpPort int, fileContent embed.FS) *WebRtcEngine {
	c := &WebRtcEngine{
		fileContent:    fileContent,
		streamPushList: map[string]*WeSocketWebrtcConnectionPush{},
	}
	c.api = webrtc.NewAPI(c.getMuxOptions(muxUdpPort)...)

	return c
}

func (tis *WebRtcEngine) GetWebrtcConnection(name string) *WeSocketWebrtcConnectionPush {
	tis.streamPushListMux.Lock()
	defer tis.streamPushListMux.Unlock()

	for _, conn := range tis.streamPushList {
		if conn.Name == name {
			return conn
		}
	}

	return nil
}

func (tis *WebRtcEngine) GetRtspConnection(name string) *RtspSession {
	if tis.rtspServer == nil {
		return nil
	}

	return tis.rtspServer.GetRtspConnection(name)
}

func (tis *WebRtcEngine) getMuxOptions(muxUdpPort int) []func(*webrtc.API) {
	// Listen on UDP Port 2000, will be used for all WebRTC traffic
	udpListener, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4zero,
		Port: muxUdpPort,
	})
	if err != nil {
		panic(err)
	}

	_ = udpListener.SetWriteBuffer(512 * 1024)
	_ = udpListener.SetReadBuffer(512 * 1024)

	log.Printf("Listening for WebRTC traffic at %s\n", udpListener.LocalAddr())

	var options []func(*webrtc.API)

	{
		// Create a SettingEngine, this allows non-standard WebRTC behavior
		settingEngine := webrtc.SettingEngine{}

		// Configure our SettingEngine to use our UDPMux. By default a PeerConnection has
		// no global state. The API+SettingEngine allows the user to share state between them.
		// In this case we are sharing our listening port across many.
		settingEngine.SetICEUDPMux(webrtc.NewICEUDPMux(nil, udpListener))

		options = append(options, webrtc.WithSettingEngine(settingEngine))
	}

	// Create a MediaEngine object to configure the supported codec
	m := &webrtc.MediaEngine{}
	{
		var codecParams = []webrtc.RTPCodecParameters{
			{
				RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, RTCPFeedback: nil},
				PayloadType:        96,
			},
			{
				RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=0", RTCPFeedback: nil},
				PayloadType:        98,
			},
			{
				RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=1", RTCPFeedback: nil},
				PayloadType:        100,
			},
			{
				RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", RTCPFeedback: nil},
				PayloadType:        125,
			},
			{
				RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", RTCPFeedback: nil},
				PayloadType:        108,
			},
			{
				RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032", RTCPFeedback: nil},
				PayloadType:        123,
			},
			{
				RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeAV1, ClockRate: 90000, RTCPFeedback: nil},
				PayloadType:        35,
			},
		}

		if false {
			err = m.RegisterCodec(
				webrtc.RTPCodecParameters{
					RTPCodecCapability: webrtc.RTPCodecCapability{
						MimeType:     webrtc.MimeTypeH264,
						ClockRate:    90000,
						Channels:     0,
						SDPFmtpLine:  "",
						RTCPFeedback: nil,
					},
					PayloadType: 96,
				},
				webrtc.RTPCodecTypeVideo,
			)
			if err != nil {
				panic(err)
			}
		} else {
			for _, param := range codecParams {
				err = m.RegisterCodec(param, webrtc.RTPCodecTypeVideo)
				if err != nil {
					panic(err)
				}
			}
		}

		err = m.RegisterCodec(
			webrtc.RTPCodecParameters{
				RTPCodecCapability: webrtc.RTPCodecCapability{
					MimeType:     webrtc.MimeTypeOpus,
					ClockRate:    48000,
					Channels:     0,
					SDPFmtpLine:  "",
					RTCPFeedback: nil,
				},
				PayloadType: 111,
			},
			webrtc.RTPCodecTypeAudio,
		)
		if err != nil {
			panic(err)
		}
	}

	// Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline.
	// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
	// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
	// for each PeerConnection.
	i := &interceptor.Registry{}

	// Use the default set of Interceptors
	if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil {
		panic(err)
	}

	options = append(options, webrtc.WithMediaEngine(m))
	options = append(options, webrtc.WithInterceptorRegistry(i))

	return options
}

// StartWebrtc 启动webrtc服务, websocket协议交换信令
func (tis *WebRtcEngine) StartWebrtc(port int, crt string, key string) {
	r := gin.Default()

	// 从定向
	r.GET("/", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "/static/index.html")
	})
	// 静态文件
	r.GET("/static/*filepath", func(c *gin.Context) {
		http.FileServer(http.FS(tis.fileContent)).ServeHTTP(c.Writer, c.Request)
	})

	r.GET("/ws/push/:name", tis.pushWebsocketConnection)
	r.GET("/ws/pull/:name", tis.pullWebsocketConnection)
	r.GET("/state", tis.state)

	tis.httpsPort = port
	log.Printf("Open https://127.0.0.1:%v to access this demo", port)
	if err := r.RunTLS(fmt.Sprintf(":%v", port), crt, key); err != nil {
		log.Println(err)
	}
}

// StartRtsp 启动rtsp服务
// 推流: ffmpeg -re -i input.mp4 -an -pix_fmt yuv420p -c:v libx264 -g 25 -preset ultrafast -tune zerolatency -f rtsp rtsp://127.0.0.1:8554/one
// webrtc拉流要选sample(rtsp的rtp流可能没有sps/pps)
func (tis *WebRtcEngine) StartRtsp(rtspPort int, rtpPort int, rtcpPort int) {
	tis.rtspServer = NewRtspServerHandler(tis)

	s := &gortsplib.Server{
		Handler:           tis.rtspServer,
		RTSPAddress:       fmt.Sprintf(":%v", rtspPort),
		UDPRTPAddress:     fmt.Sprintf(":%v", rtpPort),
		UDPRTCPAddress:    fmt.Sprintf(":%v", rtcpPort),
		MulticastIPRange:  "224.1.0.0/16",
		MulticastRTPPort:  8002,
		MulticastRTCPPort: 8003,
	}

	tis.rtspPort = rtspPort
	tis.rtpPort = rtpPort
	tis.rtcpPort = rtcpPort

	// start server and wait until a fatal error
	log.Printf("server is ready")
	if err := s.StartAndWait(); err != nil {
		log.Println(err)
	}
}

// StartRtp2Rtsp rtp转rtsp
// rtp推流: ffmpeg -re -i input.mp4 -an -pix_fmt yuv420p -c:v libx264 -g 0.01 -preset ultrafast -tune zerolatency -f rtp  rtp://127.0.0.1:5004?pkt_size=1200
func (tis *WebRtcEngine) StartRtp2Rtsp(rtpPort int) {
	listener, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: rtpPort})
	if err != nil {
		log.Println(err)
		return
	}

	tis.rtp2RtspPort = rtpPort

	defer func() {
		if err = listener.Close(); err != nil {
			panic(err)
		}
	}()

	var (
		c       *gortsplib.Client
		rtspURL = fmt.Sprintf("rtsp://127.0.0.1:%v/%v", tis.rtspPort, "one")
		buf     = make([]byte, 1600) // UDP MTU
		n       int
	)

	for {
		if n, _, err = listener.ReadFrom(buf); err != nil {
			log.Println(err)
			break
		}

		var pkt rtp.Packet
		if err = pkt.Unmarshal(buf[:n]); err != nil {
			log.Println(err)
			continue
		}

		if c == nil {
			// 连接websocket
			c = &gortsplib.Client{}
			err = c.StartPublishing(rtspURL, gortsplib.Tracks{
				&gortsplib.TrackH264{
					PayloadType: 96,
					SPS:         nil,
					PPS:         nil,
				},
			})
			if err != nil {
				log.Println(err)
				continue
			}
		}

		if c != nil {
			if err = c.WritePacketRTP(0, &pkt, true); err != nil {
				log.Println(err)
			}
		}
	}
}

// StartFile2Rtsp h264转rtsp
// 生成H264文件: ffmpeg -re -i input.mp4 -an -pix_fmt yuv420p -c:v libx264 -g 25 -preset ultrafast -f h264 2.h264
func (tis *WebRtcEngine) StartFile2Rtsp(filename string) {
	time.Sleep(time.Second)

	f, err := os.Open(filename)
	if err != nil {
		log.Println(err)
		return
	} else {
		defer func() {
			_ = f.Close()
		}()
	}

	reader, err := h264reader.NewReader(f)
	if err != nil {
		log.Println(err)
		return
	}

	var (
		c       *gortsplib.Client
		rtspURL = fmt.Sprintf("rtsp://127.0.0.1:%v/%v", tis.rtspPort, "one")
	)

	c = &gortsplib.Client{}
	err = c.StartPublishing(rtspURL, gortsplib.Tracks{
		&gortsplib.TrackH264{
			PayloadType: 96,
			SPS:         nil,
			PPS:         nil,
		},
	})
	if err != nil {
		log.Println(err)
		return
	}

	var (
		clockRate  = float64(90000)
		packetizer = rtp.NewPacketizer(
			1200,
			96,
			222,
			&codecs.H264Payloader{},
			rtp.NewRandomSequencer(),
			uint32(clockRate),
		)

		samples = uint32((time.Millisecond * 40).Seconds() * clockRate)
	)

	for {
		s := time.Now()
		nal, err := reader.NextNAL()
		if err != nil {
			log.Println("read NextNAL fail. ", err)
			break
		}
		if nal.UnitType != h264reader.NalUnitTypeCodedSliceNonIdr {
			log.Printf("unit type: %v, data len: %v", nal.UnitType.String(), len(nal.Data))
		}

		packets := packetizer.Packetize(nal.Data, samples)
		for _, packet := range packets {
			if err = c.WritePacketRTP(0, packet, true); err != nil {
				log.Println(err)
			}
		}

		d := time.Millisecond*40 - time.Since(s)
		if d > 0 {
			time.Sleep(d)
		}
	}
}

// state 状态信息
func (tis *WebRtcEngine) state(c *gin.Context) {
	type WebRtcPushedInfo struct {
		Name  string
		Uid   string
		Stats any
	}
	type RtspPushedInfo struct {
		Name   string
		Tracks any
	}

	type StateInfo struct {
		RtspPort     int
		RtpPort      int
		RtcpPort     int
		HttpsPort    int
		Rtp2RtspPort int

		WebRtcPushed []WebRtcPushedInfo
		RtspPushed   []RtspPushedInfo
	}

	var info = &StateInfo{
		RtspPort:     tis.rtspPort,
		RtpPort:      tis.rtpPort,
		RtcpPort:     tis.rtcpPort,
		HttpsPort:    tis.httpsPort,
		Rtp2RtspPort: tis.rtp2RtspPort,
		WebRtcPushed: []WebRtcPushedInfo{},
		RtspPushed:   []RtspPushedInfo{},
	}

	tis.streamPushListMux.Lock()
	for _, push := range tis.streamPushList {
		info.WebRtcPushed = append(info.WebRtcPushed, WebRtcPushedInfo{
			Name:  push.Name,
			Uid:   push.UID,
			Stats: push.pc.GetStats(),
		})
	}
	tis.streamPushListMux.Unlock()

	rtspSessionList := tis.rtspServer.GetRtspConnectionList()
	for _, session := range rtspSessionList {
		info.RtspPushed = append(info.RtspPushed, RtspPushedInfo{
			Name:   session.path,
			Tracks: session.stream.Tracks(),
		})
	}

	c.JSON(http.StatusOK, info)
}
